13

对于 swiper,只要做过轮播图的童鞋应该都再熟悉不过了。这是一个很强大的图片轮播插件,本身无任何第三方库依赖,即插即用。api 文档很清晰,所以很快能够上手。但是,再好的插件也会出现令人不愉快的地方,当然,今天所讨论的并不是插件本身的问题,只是开发者是按照常规做法去使用,而恰好此时出现了令人费解的问题。

现象

在使用 swiper 这个库的时候,一旦设置 loop:true 的时候,会遇到 dom 绑定事件无法触发的问题。

环境

vuejs 2.5.16
swiper 4.3.3
vue-awesome-swiper 3.1.3

采坑

下面来说说我是怎么一步一步采坑并最终解决这个问题的。这里只贴出关键性的代码片段。

第一版

这是最常规的做法,把 click 事件绑定在 dom 上。但有两点不足之处:

  • click 点击事件有时候无任何反应,并且这种情况必现
  • 点击下面的 title,并不能触发 click 事件,因为并没有绑定

html 代码

<div class="banner" v-if="bannerList.length">
  <swiper :options="swiperOption" ref="mySwiper">
    <swiper-slide
      v-for="(banner, index) in bannerList"
      :key="banner.id"
      @click.native="handleClickSlide(index)">
      <div class="banner-item">
        <img :src="banner.imgUrl" alt="news">
        <p>{{banner.title}}</p>
      </div>
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
  </swiper>
</div>

js 代码

new Vue({
  el: '#app',
  data: function () {
    return {
      swiperOption: {     // 轮播配置
        width: window.innerWidth,
        autoplay: {
          delay: 3000
        },
        loop: true,   // 循环滚动
        pagination: {   // 分页器
          el: '.swiper-pagination'
        },
        preventLinksPropagation: false   // 阻止点击事件冒泡
      },
      bannerList: [
        {
          id: '1',
          title: '世界杯揭幕战-超新星1球2助攻俄罗斯5-0沙特 格里兹曼宣布留马竞',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/AYes-hcyszrz3457297.jpg'
        },
        {
          id: '2',
          title: '颜值满分!世界杯首日美女球迷盘点',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/H3Wz-hcyszrz4804003.jpg'
        },
        {
          id: '3',
          title: '盘点历届世界杯大比分“屠杀”',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/FNuk-hcyszrz4805039.jpg'
        }
      ]
    }
  },
  methods: {
    // 坑在这里:
    // 会发现有的时候,click 事件点击无反应,而且这种情况是必现的
    handleClickSlide(index) {
      console.log('handleClickSlide current index', index);
    }
  }
});

改良的第二版

解决了上述两个问题,但同时也存在以下几个问题

  • 常用的 activeIndex (用来标识当前点击的第几张图片),但控制台输出的值是错乱的
  • 左右作切换的时候,activeIndex 的值也是错乱的

html 代码

<div class="banner" v-if="bannerList.length">
  <swiper :options="swiperOption" ref="mySwiper" @click.native="handleClickSlide">
    <swiper-slide
      v-for="(banner, index) in bannerList"
      :key="banner.id">
      <div class="banner-item">
        <img :src="banner.imgUrl" alt="news">
        <p>{{banner.title}}</p>
      </div>
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
  </swiper>
</div>

js 代码

new Vue({
  el: '#app',
  data: function () {
    return {
      swiperOption: {     // 轮播配置
        width: window.innerWidth,
        autoplay: {
          delay: 3000
        },
        loop: true,   // 循环滚动
        pagination: {   // 分页器
          el: '.swiper-pagination'
        },
        preventLinksPropagation: false   // 阻止点击事件冒泡
      },
      bannerList: [
        {
          id: '1',
          title: '世界杯揭幕战-超新星1球2助攻俄罗斯5-0沙特 格里兹曼宣布留马竞',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/AYes-hcyszrz3457297.jpg'
        },
        {
          id: '2',
          title: '颜值满分!世界杯首日美女球迷盘点',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/H3Wz-hcyszrz4804003.jpg'
        },
        {
          id: '3',
          title: '盘点历届世界杯大比分“屠杀”',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/FNuk-hcyszrz4805039.jpg'
        }
      ]
    }
  },
  computed: {
    swiper() {
      return this.$refs.mySwiper.swiper;
    }
  },
  methods: {
    // 坑在这里
    // 一开始点击第一张图片,控制台输出的 activeIndex 竟然是 1,难道不应该是 0吗?
    // 并且一个循环之后,点击第一张图片, 控制台输出的 activeIndex 竟然变成了 4。。。
    handleClickSlide() {
      // 这个应该是最为想到一个属性,用来标识当前点击图片的索引
      const {activeIndex} = this.swiper && this.swiper;
      console.log('handleClickSlide current index', activeIndex);
    }
  }
});

最终版

通过 swiper 强大的 api 文档,解决了上述出现的几个问题。关键点在于:当 loop 设置为 true 的时候,不能再用 activeIndex 或者 clickedIndex。只能用 realIndex。官方的解释为:当前活动块的索引,与 activeIndex 不同的是,在 loop 模式下不会将复制的块的数量计算在内。
点击事件不能绑定在 dom
不过稍不注意,也会出现新的坑(代码里有指出)

html代码

<div class="banner" v-if="bannerList.length">
  <swiper :options="swiperOption" ref="mySwiper">
    <swiper-slide
      v-for="(banner, index) in bannerList"
      :key="banner.id">
      <div class="banner-item">
        <img :src="banner.imgUrl" alt="news">
        <p>{{banner.title}}</p>
      </div>
    </swiper-slide>
    <div class="swiper-pagination" slot="pagination"></div>
  </swiper>
</div>

js 代码

let vm = null;
new Vue({
  el: '#app',
  data: function () {
    return {
      swiperOption: {     // 轮播配置
        width: window.innerWidth,
        autoplay: {
          delay: 3000
        },
        loop: true,   // 循环滚动
        pagination: {   // 分页器
          el: '.swiper-pagination'
        },
        on: {
          click: function () {
            // 这里有坑
            // 需要注意的是:this 指向的是 swpier 实例,而不是当前的 vue, 因此借助 vm,来调用 methods 里的方法 
            // console.log(this); // -> Swiper
            // 当前活动块的索引,与activeIndex不同的是,在loop模式下不会将 复制的块 的数量计算在内。           
            const realIndex = this.realIndex;
            vm.handleClickSlide(realIndex); 
          }
        },
        preventLinksPropagation: false   // 阻止点击事件冒泡
      },
      bannerList: [
        {
          id: '1',
          title: '世界杯揭幕战-超新星1球2助攻俄罗斯5-0沙特 格里兹曼宣布留马竞',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/AYes-hcyszrz3457297.jpg'
        },
        {
          id: '2',
          title: '颜值满分!世界杯首日美女球迷盘点',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/H3Wz-hcyszrz4804003.jpg'
        },
        {
          id: '3',
          title: '盘点历届世界杯大比分“屠杀”',
          imgUrl: 'http://n.sinaimg.cn/sports/180/w640h340/20180615/FNuk-hcyszrz4805039.jpg'
        }
      ]
    }
  },
  computed: {
    swiper() {
      return this.$refs.mySwiper.swiper;
    }
  },
  created() {
    vm = this;
  },
  methods: {
    handleClickSlide(index) {
      console.log('handleClickSlide current index', index);
    }
  }
});

希望借此可以帮助遇到此问题的小伙伴,祝大家的生活中再无 bug。


文博
3.3k 声望223 粉丝

叩首问路,码梦为生~